package dviz.visualAspect;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.HashSet;

import dviz.visualAnnotation.ConstructMap;
import dviz.visualAnnotation.FunctionMap;
import dviz.visualAnnotation.AttachVO;
import dviz.visualAnnotation.Param;
import dviz.visualAnnotation.PropertyMap;
import dviz.visualAspect.MetaPropertyMap.InterpolateType;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
import javassist.Translator;
import javassist.bytecode.AccessFlag;

public class AnnotationClassLoader extends VisualizedClassLoader {
	public AnnotationClassLoader(FunctionInvokeHandler functionInvokeHandler)
			throws Exception {
		super(functionInvokeHandler);
	}

	@Override
	public Translator getTranslator() {
		// TODO Auto-generated method stub
		return new Translator() {

			@Override
			public void onLoad(ClassPool pool, String classname)
					throws NotFoundException, CannotCompileException {
				try {
					// Clazz is tagged with visualization annotation
					CtClass clazz = pool.get(classname);

					{
						// If we are in the class that this property
						// is annotated, we add a dummy function for
						// retrieving that field.
						for (CtField f : clazz.getDeclaredFields()) {
							PropertyMap pmap = null;
							for (Object obj : f.getAnnotations()) {
								if (obj instanceof PropertyMap) {
									pmap = (PropertyMap) obj;
									break;
								}
							}

							// Not annotated with propertyMap
							if (pmap == null)
								continue;
							String fieldName = f.getName();
							String propertyName = null;
							// if (pmap.value().trim().length() == 0)
							if (pmap.PropertyName().trim().length() == 0)
								propertyName = fieldName;
							else
								propertyName = pmap.PropertyName();
							if (pmap.FieldType() == PropertyMap.FieldType.FIELD) {
								String dummyName = generateDummyFunction(propertyName);
								StringWriter src = new StringWriter();
								PrintWriter dummyWriter = new PrintWriter(src);

								dummyWriter.printf("public Object %s(){\n",
										dummyName);
								dummyWriter.printf("return ($w)(%s);",
										fieldName);
								dummyWriter.printf("}\n", dummyName);
								CtMethod dummyMethod = CtNewMethod.make(src
										.toString(), clazz);
								clazz.addMethod(dummyMethod);
								System.out
										.println("AnnotationClassLoader->onLoad() Adding DummyFunction:"
												+ dummyName
												+ "@"
												+ clazz.getName());
							}
						}
					}

					AttachVO map = null;

					// If any of its super class is tagged , this class inherits
					{
						CtClass current = clazz;
						while (current != null && map == null) {
							for (Object o : current.getAnnotations()) {
								if (o instanceof AttachVO) {
									map = (AttachVO) o;
									break;
								}
							}
							current = current.getSuperclass();
						}
					}

					// If class is not tagged with object map, than it's not a
					// visualizable class
					if (map == null)
						return;
					System.out
							.println("VisualziedClassLoader->onLoad() Decorating Class->"
									+ classname);

					{
						// Putting callback for Finalizing to monitor the GC of
						// this object
						// CtMethod method=clazz.getMethod("finalize", "()V");
						// if(method!=null){
						// //Class already declared this function
						// method.insertBefore("");
						// }else{
						// //For non-declared
						//							
						// }
						// TODO: Add finalize callback function
						// CtMethod method = CtNewMethod.make(
						// "public void finalize(){}", clazz);
						// clazz.addMethod(method);
					}
					// Let this class inherit index interface
					{
						// clazz.addInterface()
						CtClass indexObj = classPool.get(INDEXOBJECT_INTERFACE);
						clazz.addInterface(indexObj);
					}

					// Add function handler instance as STATIC field
					// AnnotationHelper.putAnnotation(clazz.getName(), map);
					CtField handlerField = new CtField(pool.get(HANDLER_CLASS),
							"__HANDLER_FIELD", clazz);
					{

						CtField f = new CtField(CtClass.intType, "__INDEX",
								clazz);
						clazz.addField(f);

						CtMethod getIndex = CtNewMethod.make(
								"public int __getIndex() {return __INDEX; }",
								clazz);
						clazz.addMethod(getIndex);

						CtMethod setIndex = CtNewMethod
								.make(
										"public void __setIndex(int indx) {__INDEX=indx;}",
										clazz);
						clazz.addMethod(setIndex);

					}
					handlerField.setModifiers(AccessFlag.STATIC);

					clazz
							.addField(
									handlerField,
									CtField.Initializer
											.byExpr("dviz.visualAspect.VisualizedClassLoader.getHandlerByLoader(($w)"
													+ UID + ")"));
					// Add call back to the invocation handler
					{
						CtBehavior methods[] = clazz.getDeclaredBehaviors();

						for (CtBehavior ctm : methods) {

							Object behaviorMap = null;
							if (ctm instanceof CtMethod) {
								for (Object o : ctm.getAnnotations()) {
									if (o instanceof FunctionMap)
										behaviorMap = o;
								}
								if (behaviorMap == null)
									continue;
							} else {
								for (Object o : ctm.getAnnotations()) {
									if (o instanceof ConstructMap)
										behaviorMap = o;
								}
								/*
								 * If there is no constructor map for this
								 * constructor, use the default value instead of
								 * none.
								 * 
								 * NOTE: This is the typical weirdness caused by
								 * using annotation, may be should use outside
								 * decoration file instead
								 */
								if (behaviorMap == null)
									continue;
							}
							System.out.printf("\tDecorating Method->%s\n", ctm
									.getName());
							String signature = ctm.getLongName();

							StringWriter codeString = new StringWriter();
							PrintWriter interceptCode = new PrintWriter(
									codeString);

							interceptCode.printf("{\n");
							interceptCode.printf(
									"%s __INVOKATION = new %s();\n",
									INVOKATION_CLASS, INVOKATION_CLASS);
							interceptCode.printf(
									"__INVOKATION.setClassName(\"%s\");\n",
									clazz.getName());
							interceptCode.printf(
									"__INVOKATION.setVisualObject(\"%s\");\n",
									map.value());
							if (ctm instanceof CtMethod) {
								interceptCode
										.printf(
												"__INVOKATION.setActionName(\"%s\");\n",
												((FunctionMap) behaviorMap)
														.value());

								interceptCode.printf(
										"__INVOKATION.setType(%d);\n",
										Invokation.TYPE_METHOD);
							} else {
								interceptCode
										.printf(
												"__INVOKATION.setConstructType(\"%s\");\n",
												((ConstructMap) behaviorMap)
														.value());
								interceptCode.printf(
										"__INVOKATION.setType(%d);\n",
										Invokation.TYPE_CONSTRUCTOR);
							}
							interceptCode
									.printf("__INVOKATION.parameters = new java.util.HashMap();\n");
							Object[][] paramsAnno = ctm
									.getParameterAnnotations();
							for (int i = 0; i < paramsAnno.length; i++) {
								for (int j = 0; j < paramsAnno[i].length; j++) {

									Object o = paramsAnno[i][j];

									if (o instanceof Param) {
										interceptCode
												.printf(
														"__INVOKATION.parameters.put(\"%s\",($w)$%d);",
														((Param) o).value(),
														i + 1);
										break;
									}
								}
							}
							interceptCode
									.printf("__INVOKATION.setContext($0);\n");
							interceptCode
									.printf("__HANDLER_FIELD.handleFunctionCall(__INVOKATION);\n");
							interceptCode.printf("}\n");
							String codeStr = codeString.getBuffer().toString();

							System.out.println(codeStr);
							// if (ctm instanceof CtConstructor) {
							if (ctm instanceof CtConstructor) {
								((CtConstructor) ctm).insertBeforeBody(codeStr);
							} else
								ctm.insertAfter(codeStr);
							// } else {
							// ctm.insertBefore(codeStr);
							// }
						}
					}
					// End of call back interception code

					// Fetch Property information within class
					{
						// PropertyMap< AnimationObjectPropertyName, FieldName>
						HashMap<String, MetaPropertyMap> propertyMap = new HashMap<String, MetaPropertyMap>();
						HashSet<String> set = new HashSet<String>();
						CtClass current = clazz;

						// Recursively go from sub class to super class
						while (current != null) {
							// System.out.println("On Class:"+current.getName());
							for (CtField f : current.getDeclaredFields()) {
								// If sub class field override super class's
								// if (set.contains(f.getName()))
								// continue;
								set.add(f.getName());
								// Check if this field contain property map
								// annotation
								PropertyMap pmap = null;
								for (Object obj : f.getAnnotations()) {
									if (obj instanceof PropertyMap) {
										pmap = (PropertyMap) obj;
										break;
									}
								}

								// Not annotated with propertyMap
								if (pmap == null)
									continue;
								String fieldName = f.getName();
								String propertyName = null;
								// if (pmap.value().trim().length() == 0)
								if (pmap.PropertyName().trim().length() == 0)
									propertyName = fieldName;
								else
									propertyName = pmap.PropertyName();

								String dummyName = null;
								if (pmap.FieldType() == PropertyMap.FieldType.FIELD) {
									dummyName = generateDummyFunction(propertyName);
								} else if (pmap.FieldType() == PropertyMap.FieldType.FUNCTION) {
									dummyName = fieldName;
								} else
									throw new RuntimeException(
											"Not implemented field type:"
													+ pmap.FieldType());
								// Make property field accessible to all since
								// framework might need to get the value
								// directly

								MetaPropertyMap meta = new MetaPropertyMap();
								meta.functionName = dummyName;
								meta.type = pmap.MapType();
								meta.interpolateType = InterpolateType.INT_LAST_VALUE;
								meta.interpolateParameter = new HashMap<String, String>();
								propertyMap.put(propertyName, meta);
								// System.out.println(f.getName());

							}
							current = current.getSuperclass();
						}
						clazzPropertyMap.put(clazz.getName(), propertyMap);
					}
					// End of Fetch Property Information
				} catch (Exception e) {
					System.err.println("Class load failure:" + classname);
					e.printStackTrace();
				}
			}

			@Override
			public void start(ClassPool pool) throws NotFoundException,
					CannotCompileException {
			}

			public final String generateDummyFunction(String propertyName) {
				return "__" + propertyName + "_"
						+ Integer.toHexString(propertyName.hashCode());
			}
		};
	}

}
